# -*- mode: python; coding: utf-8; -*-
#---------------------------------------------------------------------------##
#
# Copyright (C) 1998-2003 Markus Franz Xaver Johannes Oberhumer
# Copyright (C) 2003 Mt. Hood Playing Card Co.
# Copyright (C) 2005-2010 Skomoroh
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
#---------------------------------------------------------------------------##
__docformat__ = "restructuredtext en"

import logging

Logger = logging.getLogger('PySolFC.StackMahjongg')

from ..gamelib import state
from ..packlib import info as cs
from .. import comparecards as cmp_cards
from .. import pilelib as stack
from .. import hint
from ..viewlib import canvas
from ..viewlib import constants as vcnst
from ..utillib import misc
from .. import settings as c
from ..pilelib import move as a_move

class Hint(hint.AbstractHint):
    # FIXME: no intelligence whatsoever is implemented here
    def compute_hints(self):
        """
        compute_hints() -> None

        compute hints - main hint intelligence
        """
        rows = self._stacks['set']['rows']
        # get free stacks
        stacks = [s for s in rows if s.cards and not s.cardsBlock()]
        # find matching tiles
        i = 0
        for from_stack in stacks:
            for to_stack in stacks[i + 1:]:
                if to_stack._sequence(from_stack.cards[-1:] +
                                      to_stack.cards[-1:]):
                    rb = from_stack.blockmap
                    tb = to_stack.blockmap
                    score = (10000 + 1000 * (len(rb['below']) + len(tb['below'])) +
                             len(rb['all_left']) + len(rb['all_right']) +
                             len(tb['all_left']) + len(tb['all_right']))
                    self.add_hint(score, 1, from_stack, to_stack)
            i += 1


def iter_positions(level, tx, ty):
    # tilemap - each tile covers 4 positions
    yield (level, tx, ty,)
    yield (level, tx + 1, ty,)
    yield (level, tx, ty + 1,)
    yield (level, tx + 1, ty + 1,)


def blockmap(rows, tiles, tilemap):
    # compute blockmap
    for pile in rows:
        level, tx, ty = tiles[pile.id]
        above, below, left, right = {}, {}, {}, {}
        # above blockers
        for tl in range(level + 1, level + 2):
            for position in iter_positions(tl, tx, ty):
                above[tilemap.get(position)] = True
        # below blockers
        for tl in range(level):
            for position in iter_positions(tl, tx, ty):
                below[tilemap.get(position)] = True
        # left blockers
        left[tilemap.get((level, tx - 1, ty))] = True
        left[tilemap.get((level, tx - 1, ty + 1))] = True
        # right blockers
        right[tilemap.get((level, tx + 2, ty))] = True
        right[tilemap.get((level, tx + 2, ty + 1))] = True
        # sanity check - assert that there are no overlapping tiles
        for position in iter_positions(level, tx, ty):
            assert tilemap.get(position) is pile
        above = tuple(filter(None, above.keys()))
        below = tuple(filter(None, below.keys()))
        left = tuple(filter(None, left.keys()))
        right = tuple(filter(None, right.keys()))
        # assemble
        pile.blockmap = dict(above=above, below=below,
                             left=left, right=right, all_left={},
                             all_right={})

    def get_all(pile, side):
        all_side = "all_{}".format(side)
        for neighbor in pile.blockmap[side]:
            if neighbor.blockmap[all_side] is None:
                get_all(neighbor, side)
            blockmap = pile.blockmap[all_side]
            blockmap.update(neighbor.blockmap[all_side])
            blockmap[neighbor] = True

    for r in rows:
        get_all(r, 'left')
        get_all(r, 'right')

    for r in rows:
        r.blockmap['all_left'] = tuple(r.blockmap['all_left'].keys())
        r.blockmap['all_right'] = tuple(r.blockmap['all_right'].keys())


class Foundation(stack.OpenStack):
    _BOTTOM_IMAGE_ = None

    def __init__(self, x, y, game, **cap):
        ncards = game['ncards']
        misc.kwdefault(cap, max_move=0, max_accept=0, max_cards=ncards)
        super(Foundation, self).__init__(x, y, game, **cap)

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack, cards) -> bool

        Do not accept any cards - pairs will get delivered by do_pair_drop().
        """
        return False

    def cardsBlock(self):
        return True

    def _position(self, card):
        super(Foundation, self)._position(card)
        foundations = self.game.s['foundations']
        for i in range(3, -1, -1):
            for j in range(9):
                n = i * 9 + j
                if foundations[n].cards:
                    foundations[n].group.raise_item()
        return


class RowStack(stack.OpenStack):
    _BOTTOM_IMAGE_ = None

    def __init__(self, x, y, game, **cap):
        misc.kwdefault(cap, max_move=1, max_accept=1, max_cards=2,
                       base_rank=cs.GAP)
        super(RowStack, self).__init__(x, y, game, **cap)

    def cardsBlock(self):
        # any of above blocks
        for stack in self.blockmap['above']:
            if stack.cards:
                Logger.debug("blocks above")
                return True
        # any of left blocks - but we can try right as well
        for stack in self.blockmap['left']:
            if stack.cards:
                Logger.debug("blocks left")
                break
        else:
            return False
        # any of right blocks
        for stack in self.blockmap['right']:
            if stack.cards:
                Logger.debug("blocks right")
                return True
        return False

    def _sequence(self, cards):
        return cmp_cards.isSameMahjonggRank(cards)

    def cardsBuild(self, from_stack, cards):
        if not super(RowStack, self).cardsBuild(from_stack, cards):
            return False
        return self._sequence(self.cards[-1:] + cards)

    def cardFlips(self):
        return False

    def cardsDrop(self, stacks):
        return (None, 0)

    def move_pile(self, ncards, to_stack, **kw):
        assert ncards == 1
        card = self.TOPCARD
        if card['suit'] == 3:
            if card['rank'] >= 8:  # Seasons
                i = 35
            elif card['rank'] >= 4:  # Flowers
                i = 34
            else:
                i = 30 + card['rank']
        elif card['rank'] == 9:
            i = 27 + card['suit']
        else:
            i = card['suit'] * 9 + card['rank']
        game = self.game
        if game.can_play_sound:
            game._play_sound('droppair', priority=10)
        game._move_do(a_move.ADropPairMove(self, to_stack,
                                           dropstack=game.s['foundations'][i],
                                           **kwargs), reset=True)  # low level
        self.fill_stack()
        to_stack.fill_stack()
        self.game._update_text()

    # Mahjongg special overrides
    def _position(self, card):
        """
        _position(card) -> None

        Preserve the relative stacking order to keep the pseudo 3D look.
        """
        super(RowStack, self)._position(card)
        rows = [s for s in self.game.s['rows'][:self.id] if s.cards]
        if rows:
            self.group.raise_item(rows[-1].group)
            return
        rows = [s for s in self.game.s['rows'][self.id + 1:] if s.cards]
        if rows:
            self.group.lower_item(rows[0].group)

    def init_bindings(self):
        """
        In Mahjongg games type there are a lot of stacks, so we optimize and
        don't create bindings that are not used anyway.
        """
        group = self.group
        group.bind("<1>", self.__clickEventHandler)
        group.bind("<3>", self.__controlclickEventHandler)
        group.bind("<Control-1>", self.__controlclickEventHandler)

    def __defaultClickEventHandler(self, event, handler):
        self.game.event_handled = True  # for Game.undoHandler
        self.game.demo_stop()
        if self.game.busy:
            return vcnst.EVENT_HANDLED
        handler(event)
        return vcnst.EVENT_HANDLED

    def __clickEventHandler(self, event):
        return self.__defaultClickEventHandler(event, self.clickHandler)

    def __controlclickEventHandler(self, event):
        return self.__defaultClickEventHandler(event,
                                               self.controlclickHandler)

    def clickHandler(self, event):
        game = self.game
        drag = game.drag
        # checks
        if not self.cards:
            return True
        from_stack = drag['stack']
        if from_stack is self:
            # remove selection
            if self.game.can_play_sound:
                self.game._play_sound("nomove")
            self._stopDrag()
            return True
        if self.cardsBlock():
            return True
        # possible move
        if from_stack:
            if self.cardsBuild(from_stack, from_stack.cards):
                self._stopDrag()
                # this code actually moves the tiles
                game.move(1, self, from_stack, frames=0, sound=True)
                return True
        drag['stack'] = self
        if self.game.can_play_sound:
            self.game._play_sound("startdrag")
        # create the shade image (see stack.py, _updateShade)
        if drag['shade_img']:
            drag['shade_img'].delete()
            drag['shade_img'] = None
        img = self.game.images.highlight
        if img is None:
            return True
        img = canvas.ImageItem(game.canvas, misc.Point(self.x, self.y),
                               group=self.group, image=img)
        drag['shade_img'] = img
        # raise/lower the shade image to the correct stacking order
        img.raise_item(card._item)
        drag['shade_stack'] = self
        return True

    def _cancelDrag(self, event=None):
        if event is None:
            self._stopDrag()

    def _locate(self, event):
        """
        We need to override this because the shade may be hiding the tile
        (from Tk's stacking view)
        """
        return len(self.cards) - 1